package com.laomuji.android.mvvm.base

import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import androidx.annotation.IntDef
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.laomuji.android.mvvm.R
import com.laomuji.android.mvvm.base.lifecycle.LifecycleDialogFragment
import com.laomuji.android.mvvm.utils.ReflectionUtil

/**
 * 所有DialogFragment的基类,尽量保持和Dialog一样的体验
 *
 * 已知问题:
 * 1. DialogFragment存在一个bug,最外层的宽度和高度会被覆盖.
 *    A: 使用 minWidth 和 minHeight.
 *    B: 再包一层 ViewGroup,使用这个ViewGroup的宽高.
 *    C: 重写 [BaseDialogFragment.getDialogWidth] 和 [BaseDialogFragment.getDialogHeight].
 * @author laomuji666
 * @since 2024/2/20
 */
abstract class BaseDialogFragment<VB : ViewBinding, VM : ViewModel> : LifecycleDialogFragment() {
    /**
     * MVVM
     */
    open lateinit var viewBinding: VB
    open lateinit var viewModel: VM

    /**
     * 是否已经销毁过了
     */
    private var isCleared = false

    /**
     * 是否已经初始化过了
     */
    private var isInitialized = false

    /**
     * 主线程handler
     */
    private var mainHandlerIsInit = false
    open val mainHandler: Handler by lazy {
        mainHandlerIsInit = true
        Handler(Looper.getMainLooper())
    }

    /**
     * 在attach前设置Dialog的Style,防止设置失败
     */
    override fun onAttach(context: Context) {
        setStyle(STYLE_NORMAL, getDialogTheme())
        super.onAttach(context)
    }

    /**
     * 子类可以重写设置Dialog的Theme
     * @return Dialog的Theme
     */
    protected open fun getDialogTheme(): Int = R.style.DialogTheme

    /**
     * 初始化ViewBinding和ViewModel
     */
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //恢复清理状态
        isCleared = false

        //恢复初始化状态
        isInitialized = false

        //初始化MVVM
        initViewBinding(inflater, container)
        initViewModel()

        return viewBinding.root
    }

    /**
     * 在onDestroyView时清理页面
     */
    override fun onDestroyView() {
        clearPage()
        super.onDestroyView()
    }

    /**
     * 在onStart时初始化页面和数据
     */
    override fun onStart() {
        super.onStart()

        //在onStart时 可能没有销毁页面 不需要重复初始化
        if(isInitialized){
            return
        }
        isInitialized = true

        //只能在onStart中初始化Dialog的尺寸和位置
        initDialogParams()

        //初始化页面和数据
        initView()
        initData()
    }

    /**
     * 拦截创建Dialog
     * 为Dialog设置一些参数
     */
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState)
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
        dialog.setCanceledOnTouchOutside(isCanceledOnTouchOutside())
        return dialog
    }

    /**
     * 初始化Dialog
     */
    private fun initDialogParams() {
        val window: Window = dialog!!.window!!
        val attributes = window.attributes

        //设置Dialog的位置和大小
        attributes.gravity = getDialogGravity()
        attributes.width = getDialogWidth()
        attributes.height = getDialogHeight()

        //设置是否可以点击Dialog外部
        if (isClickOnTouchOutsize()) {
            attributes.flags = attributes.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        }

        //弹窗弹出动画
        if (isOpenBottomAnim()) {
            attributes.windowAnimations = R.style.DialogBottomAnimStyle
        }

        window.attributes = attributes
    }

    /**
     * 结束Fragment时清理
     */
    private fun clearPage(){
        //不重复清理数据
        if(isCleared){
            return
        }
        isCleared = true

        //移除handler
        if(mainHandlerIsInit){
            mainHandler.removeCallbacksAndMessages(null)
        }
    }

    /**
     * 初始化ViewBinding
     */
    private fun initViewBinding(inflater: LayoutInflater, container: ViewGroup?) {
        val viewBindingCls = ReflectionUtil.getGenericsClass<VB>(javaClass, 0) as Class<VB>
        val method = viewBindingCls.getMethod(
            "inflate",
            LayoutInflater::class.java,
            ViewGroup::class.java,
            Boolean::class.java
        )
        @Suppress("UNCHECKED_CAST")
        viewBinding = method.invoke(null,inflater,container,false) as VB
    }

    /**
     * 初始化ViewModel
     */
    private fun initViewModel() {
        val genericsClass = ReflectionUtil.getGenericsClass<VM>(javaClass, 1)
            ?: throw RuntimeException("ViewModel init Failed!")
        viewModel = ViewModelProvider(this)[genericsClass]
    }

    /**
     * 初始化页面
     * 包括对页面的初始化和获取Intent的数据
     */
    protected abstract fun initView()

    /**
     * 初始化数据
     * 包括从后台获取数据和监听ViewModel
     */
    protected abstract fun initData()

    /**
     * 是否可以点击Dialog外部
     * 不会拦截Dialog外部的点击事件
     * @see [isCanceledOnTouchOutside] 冲突
     */
    protected open fun isClickOnTouchOutsize(): Boolean = false

    /**
     * 是否点击Dialog外的区域关闭Dialog
     * 会拦截Dialog外部的点击事件
     * [isClickOnTouchOutsize]返回true时,点击外部关闭Dialog事件会被忽略
     */
    protected open fun isCanceledOnTouchOutside() = true

    /**
     * 是否打开从底部弹出动画
     */
    protected open fun isOpenBottomAnim() = true

    /**
     * getDialogGravity的返回值
     * Dialog在屏幕中的位置
     */
    @Retention(AnnotationRetention.SOURCE)
    @IntDef(Gravity.CENTER, Gravity.TOP, Gravity.BOTTOM, Gravity.START, Gravity.END)
    annotation class DialogGravityFlag

    /**
     * @return Dialog的位置
     */
    @DialogGravityFlag
    protected open fun getDialogGravity(): Int = Gravity.CENTER

    /**
     * getDialogGravity的返回值
     * Dialog在屏幕中的位置
     */
    @Retention(AnnotationRetention.SOURCE)
    @IntDef(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
    annotation class DialogSizeFlag

    /**
     * Dialog的宽度
     * MATCH_PARENT:Dialog宽度铺满,布局的宽度被覆盖
     * WRAP_CONTENT:Dialog宽度和布局的宽度相同
     * @return Dialog的宽度
     */
    @DialogSizeFlag
    protected open fun getDialogWidth(): Int = ViewGroup.LayoutParams.WRAP_CONTENT

    /**
     * Dialog的高度
     * MATCH_PARENT:Dialog高度铺满,布局的高度会被覆盖
     * WRAP_CONTENT:Dialog高度和布局的高度相同
     * @return Dialog的高度
     */
    @DialogSizeFlag
    protected open fun getDialogHeight(): Int = ViewGroup.LayoutParams.WRAP_CONTENT


    @Deprecated(
        "不要使用,请使用 [BaseDialogFragment.show]",
        ReplaceWith("this.show(manager)"),
        DeprecationLevel.ERROR
    )
    final override fun showNow(manager: FragmentManager, tag: String?) = super.showNow(manager, tag)

    @Deprecated(
        "不要使用,请使用 [BaseDialogFragment.show]",
        ReplaceWith("this.show(manager)"),
        DeprecationLevel.ERROR
    )
    final override fun show(manager: FragmentManager, tag: String?) = super.show(manager, tag)

    /**
     * 立刻显示弹窗
     * @param manager 需要使用的FragmentManager
     */
    open fun show(manager: FragmentManager) {
        //不能重复show
        if(dialog!=null && dialog!!.isShowing){
            return
        }
        super.showNow(manager, tag)
    }

    /**
     * 隐藏弹窗
     */
    override fun dismiss() {
        //不能重复dismiss
        if(dialog == null || !dialog!!.isShowing){
            return
        }
        super.dismiss()
    }
}